package jehc.cloud.iot.live.common.util;

import jehc.cloud.iot.live.common.config.Config;
import jehc.cloud.iot.live.model.CameraEntity;
import lombok.extern.slf4j.Slf4j;
import org.bytedeco.ffmpeg.avcodec.AVPacket;
import org.bytedeco.ffmpeg.avformat.AVFormatContext;
import org.bytedeco.ffmpeg.global.avcodec;
import org.bytedeco.ffmpeg.global.avutil;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.FFmpegFrameRecorder;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;

import static org.bytedeco.ffmpeg.global.avcodec.av_packet_unref;
/**
 * @Desc 拉流推流
 * @Author 邓纯杰
 * @CreateTime 2012-12-12 12:12:12
 */
@Slf4j
public class PushUtil {
	protected FFmpegFrameGrabber grabber = null;// 解码器
	protected FFmpegFrameRecorder record = null;// 编码器
	int width;// 视频像素宽
	int height;// 视频像素高
	// 视频参数
	protected int audiocodecid;
	protected int codecid;
	protected double framerate;// 帧率
	protected int bitrate;// 比特率
	// 音频参数
	// 想要录制音频，这三个参数必须有：audioChannels > 0 && audioBitrate > 0 && sampleRate > 0
	private int audioChannels;
	private int audioBitrate;
	private int sampleRate;
	// 设备信息
	private CameraEntity cameraEntity;
	public PushUtil() {
		super();
	}
	public PushUtil(CameraEntity cameraEntity) {
		this.cameraEntity = cameraEntity;
	}
	public PushUtil(CameraEntity cameraEntity,Thread nowThread) {
		this.cameraEntity = cameraEntity;
	}



	/**
	 * 选择视频源（拉流）
	 * @throws org.bytedeco.javacv.FrameGrabber.Exception
	 * @throws org.bytedeco.javacv.FrameGrabber.Exception
	 * @throws org.bytedeco.javacv.FrameGrabber.Exception
	 * @throws Exception
	 */
	public PushUtil from() throws Exception {
		Config config = ApplicationContextUtil.getBean(Config.class);
		// 解决ip输入错误时，grabber.start();出现阻塞无法释放grabber而导致后续推流无法进行；
		Socket rtspSocket = new Socket();
		Socket rtmpSocket = new Socket();
		// 建立TCP Scoket连接，超时时间1s，如果成功继续执行，否则return
		log.debug("TCPCheck开始...");
		long beginTime = System.currentTimeMillis();
		try {
			//RTSP摄像机服务器（源头）
			rtspSocket.connect(new InetSocketAddress(cameraEntity.getIp(), cameraEntity.getPort()==null?554:cameraEntity.getPort()), 2000);//建立TCP连接两秒
		} catch (IOException e) {
			setErrorCount(cameraEntity,false);//设置为异常
			if(null != grabber){
				grabber.stop();
				grabber.close();
			}
			if(null != rtspSocket){
				rtspSocket.close();
			}
			log.error("与拉流IP:" + cameraEntity.getIp() + ",端口: 554建立TCP连接失败！");
			return null;
		}
		long endTime = System.currentTimeMillis();
		long usedTime = (endTime - beginTime);//耗时
		log.info("ping源总耗时："+usedTime);

		beginTime = System.currentTimeMillis();
		try {
			//RTMP服务器（Nginx服务器或其它接收流服务器）
			rtmpSocket.connect(new InetSocketAddress(IpUtil.IpConvert(config.getStreamNginxIp()), Integer.parseInt(config.getStreamNginxTcpPort())), 2000);//建立TCP连接两秒
		} catch (IOException e) {
			setErrorCount(cameraEntity,false);//设置为异常
			if(null != grabber){
				grabber.stop();
				grabber.close();
			}
			if(null != rtspSocket){
				rtspSocket.close();
			}
			log.error("与推流IP：" + config.getStreamNginxIp() + "端口：" + config.getStreamNginxTcpPort() + " 建立TCP连接失败！");
			return null;
		}
		endTime = System.currentTimeMillis();
		usedTime = (endTime - beginTime);//耗时
		log.info("ping Nginx总耗时："+usedTime);
		log.debug("TCPCheck结束...");


		beginTime = System.currentTimeMillis();
		// 采集/抓取器
		grabber = new FFmpegFrameGrabber(cameraEntity.getRtsp());
		if (cameraEntity.getRtsp().indexOf("rtsp") >= 0) {
			grabber.setOption("rtsp_transport", "tcp");//tcp用于解决丢包问题
			grabber.setOption("stimeout", "2000000");//设置采集器构造超时时间2000000微秒=2秒
		}
		grabber.setPixelFormat(avutil.AV_PIX_FMT_YUV420P);//设置帧收集时的像素格式，默认使用AV_PIX_FMT_YUV420P，（注意：这块设置avutil.AV_PIX_FMT_RGBA的原因主要是，我们展示画面的时候是转换为Bitmap格式的。）
		grabber.setVideoCodec(avcodec.AV_CODEC_ID_H264);// h264编/解码器
		grabber.setVideoOption("vcodec", "copy");
		/*
		grabber.setOption("fflags", "nobuffer");//减少缓冲
		grabber.setOption("analyzeduration", "1000000");//码流分析时间设置，单位为微秒（目的减少分析码流的时间）
        grabber.setFrameRate(25);//设置帧率
        grabber.setVideoBitrate(2000000);//设置视频bit率
		*/
		try {
			long startTimes = System.currentTimeMillis();
			log.info("grabber.start()开始...");
			grabber.start();//采用原始包重启（即org.bytedeco.javacv.FFmpegFrameGrabber包中类）
			log.info("grabber.start()结束...");
			long endTimes = System.currentTimeMillis();
			log.info("启动grabber总耗时："+(endTimes-startTimes));

			// 开始之后ffmpeg会采集视频信息，之后就可以获取音视频信息
			width = grabber.getImageWidth();
			height = grabber.getImageHeight();
			// 若视频像素值为0，说明拉流异常，程序结束
			if (width == 0 && height == 0) {
				log.error(cameraEntity.getRtsp() + "  拉流异常！");
				grabber.stop();
				grabber.close();
				cameraEntity.setErrorCount(cameraEntity.getErrorCount()+1);
				CacheUtil.STREAMMAP.put(cameraEntity.getToken(),cameraEntity);
				return null;
			}
			//视频参数
			audiocodecid = grabber.getAudioCodec();
			codecid = grabber.getVideoCodec();
			framerate = grabber.getVideoFrameRate();//帧率
			bitrate = grabber.getVideoBitrate();//比特率
			sampleRate = grabber.getSampleRate();
			// 音频参数
			// 想要录制音频，这三个参数必须有：audioChannels > 0 && audioBitrate > 0 && sampleRate > 0
			audioChannels = grabber.getAudioChannels();
			audioBitrate = grabber.getAudioBitrate();
			if (audioBitrate < 1) {
				audioBitrate = 128 * 1000;// 默认音频比特率
			}
		} catch (Exception e) {
			log.error("ffmpeg错误信息：", e);
			setErrorCount(cameraEntity,false);//设置为异常
			if(null != grabber){
				grabber.stop();
				grabber.close();
			}
			return null;
		}
		endTime = System.currentTimeMillis();
		usedTime = (endTime - beginTime);//耗时
		log.info("拉流总耗时："+usedTime);
		return this;
	}

	/**
	 * 推流
	 * @throws Exception
	 */
	public PushUtil to() throws Exception {
		long beginTime = System.currentTimeMillis();
		log.info("进入推流...");
		// 录制/推流器
		record = new FFmpegFrameRecorder(cameraEntity.getRtmp(), width, height);
		record.setVideoOption("crf", "25");// 画面质量参数，0~51；18~28是一个合理范围
		record.setVideoOption("tune", "zerolatency"); // 降低编码延时
		record.setVideoOption("preset", "ultrafast"); // 提升编码速度
		record.setGopSize((int) grabber.getFrameRate() * 2);// 关键帧间隔，一般与帧率相同或者是视频帧率的两倍
		record.setFrameRate(framerate);
		record.setVideoBitrate(bitrate);//比特率，默认400000
		record.setSampleRate(sampleRate);
		record.setMaxDelay(500);
		record.setFormat("h264");// 录制的视频格式 flv(rtmp格式) h264(udp格式) mpegts(未压缩的udp) rawvideo
		double frameLength = grabber.getLengthInFrames(); // 帧数
		long frameTime = grabber.getLengthInTime();
		double v = frameLength * 1000 * 1000 / frameTime;
		record.setFrameRate(v);
		record.setInterleaved(true);
		record.setPixelFormat(avutil.AV_PIX_FMT_YUV420P); // yuv420p
		AVFormatContext fc = null;
		if (cameraEntity.getRtmp().indexOf("rtmp") >= 0 || cameraEntity.getRtmp().indexOf("flv") > 0) {
			// 封装格式flv
			record.setFormat("flv");
			record.setVideoCodec(codecid);
			if(cameraEntity.isPushAudio()){
				record.setAudioChannels(audioChannels);
				record.setAudioBitrate(audioBitrate);
				record.setAudioCodecName("aac");;//音频默认采用AAC（注意大华摄像机用的是G.711A对应其值应该设置为AV_CODEC_ID_PCM_ALAW，G.711MUA对应其值为AV_CODEC_ID_PCM_MULAW）
				record.setAudioCodec(avcodec.AV_CODEC_ID_AAC);;//音频默认采用AAC（注意大华摄像机用的是G.711A对应其值应该设置为AV_CODEC_ID_PCM_ALAW，G.711MUA对应其值为AV_CODEC_ID_PCM_MULAW）
	//			record.setAudioCodecName("G.711A");//音频默认采用AAC（注意大华摄像机用的是G.711A对应其值应该设置为AV_CODEC_ID_PCM_ALAW，G.711MUA对应其值为AV_CODEC_ID_PCM_MULAW）
	//			record.setAudioCodec(avcodec.AV_CODEC_ID_PCM_ALAW);//音频默认采用AAC（注意大华摄像机用的是G.711A对应其值应该设置为AV_CODEC_ID_PCM_ALAW，G.711MUA对应其值为AV_CODEC_ID_PCM_MULAW）
			}
			fc = grabber.getFormatContext();
		}
		try {
			record.start(fc);
		} catch (Exception e) {
			setErrorCount(cameraEntity,false);//设置为异常
			log.error(cameraEntity.getRtsp() + "推流异常！");
			log.error("ffmpeg错误信息：", e);
			release(grabber,record);
			return null;
		}
		long endTime = System.currentTimeMillis();
		long usedTime = (endTime - beginTime);//耗时
		log.info("推流总耗时："+usedTime);
		return this;
	}

	/**
	 * 转封装
	 * @throws org.bytedeco.javacv.FrameGrabber.Exception
	 * @throws org.bytedeco.javacv.FrameRecorder.Exception
	 * @throws InterruptedException
	 */
	public PushUtil go(Thread nowThread)throws org.bytedeco.javacv.FrameGrabber.Exception, org.bytedeco.javacv.FrameRecorder.Exception{
		log.info("开始转封装："+cameraEntity.getRtsp() );
		long err_index = 0;// 采集或推流导致的错误次数
		// 每一秒第一帧的时间
		long firstpkttime = System.currentTimeMillis();
		setErrorCount(cameraEntity,true);//恢复
		//记录dts和pts
		long dts = 0;
		long pts = 0;
		int pktindex = 0;
		int timebase = 0;
		if(null != grabber){
			grabber.flush();// 释放探测时缓存下来的数据帧，避免pts初始值不为0导致画面延时
		}
		for (int no_frame_index = 0; no_frame_index < 5 || err_index < 5;) { // 连续5次没有采集到帧则认为视频采集结束，程序错误次数超过5次即中断程序
			long startTime = System.currentTimeMillis();
			try {
				if(cameraEntity.getErrorCount()>100){
					break;
				}
				if(null != grabber){
					AVPacket pkt = grabber.grabPacket();
					if (pkt == null || pkt.size() <= 0 || pkt.data() == null) { //空包记录次数跳过
						no_frame_index++;
						err_index++;
						log.info("空包："+no_frame_index+","+err_index);
						continue;
					}

					/*if (pkt.stream_index() == 1) {// 过滤音频，跳过
						av_packet_unref(pkt);
						continue;
					}*/

					/*if (pkt.dts() == avutil.AV_NOPTS_VALUE && pkt.pts() == avutil.AV_NOPTS_VALUE || pkt.pts() < dts) {// 获取到的pkt的dts，pts异常，将此包丢弃掉。
//						log.debug("异常pkt   当前pts: " + pkt.pts() + "  dts: " + pkt.dts() + "  上一包的pts： " + pts + " dts: "+ dts);
						err_index++;
						av_packet_unref(pkt);
						continue;
					}*/

					// 矫正pkt的dts，pts每次不从0开始累加所导致的播放器无法续播问题
					pkt.pts(pts);
					pkt.dts(dts);
					err_index += (record.recordPacket(pkt) ? 0 : 1);// 将缓存空间的引用计数-1，并将Packet中的其他字段设为初始值。如果引用计数为0，自动的释放缓存空间。
					no_frame_index=0;//踩到正常 则重新重置该值

					//TODO 计算出pts、dts时间戳间隔
					timebase = grabber.getFormatContext().streams(pkt.stream_index()).time_base().den();
					pts += (timebase / (int) framerate);//pts累加
					dts += (timebase / (int) framerate);//dts累加
					av_packet_unref(pkt);//不需要编码直接把音视频帧推出去
				}
			}catch (Exception e){
				if(e.getMessage().equals("av_interleaved_write_frame() error -22 while writing interleaved video packet.")){
					log.info("警告，包解析异常：视频第一个包的dts大于第二个包的pts.");
					if(null != grabber){
						grabber.flush();
					}
					continue;
				}else if(e.getMessage().equals("av_interleaved_write_frame() error -22 while writing interleaved audio packet.")){
					log.info("警告，包解析异常：视频第一个包的dts大于第二个包的pts.");
					if(null != grabber){
						grabber.flush();
					}
					continue;
				}else if(e.getMessage().equals("av_interleaved_write_frame() error -10054 while writing interleaved audio packet.")){
                    log.error("异常：nginx 断线");
					cameraEntity.setErrorCount(101);
					setErrorCount(cameraEntity,false);//终止线程
                    break;
                }else if(e.getMessage().equals("av_write_frame() error -10053 while writing video packet.")){
					log.error("推流地址Socket连接超时："+e.getMessage()+",摄像机ip："+cameraEntity.getIp());
					cameraEntity.setErrorCount(101);
					setErrorCount(cameraEntity,false);//终止线程
					break;
				}else if(e.getMessage().equals("av_write_frame() error -10054 while writing video packet.")){
					log.error("推流地址Socket连接中断："+e.getMessage()+",摄像机ip："+cameraEntity.getIp());
					cameraEntity.setErrorCount(101);
					setErrorCount(cameraEntity,false);//终止线程
					break;
				}else if(e.getMessage().equals("av_write_frame() error -22 while writing video packet.")){
                    log.info("警告，包解析异常：视频第一个包的dts大于第二个包的pts."+cameraEntity.getRtsp());
					if(null != grabber){
						grabber.flush();
					}
					continue;
				}else{
					log.error("其它异常："+e.getMessage()+","+cameraEntity.getRtsp());
					err_index++;
					no_frame_index++;
				}
			}
		}
		release(grabber,record); // 程序正常结束销毁构造器
		log.info("转封装结束..."+cameraEntity.getRtsp() );
		return this;
	}

	/**
	 * 释放
	 * @param grabber
	 * @param record
	 * @throws org.bytedeco.javacv.FrameGrabber.Exception
	 * @throws org.bytedeco.javacv.FrameRecorder.Exception
	 */
	public void release(FFmpegFrameGrabber grabber,FFmpegFrameRecorder record) throws org.bytedeco.javacv.FrameGrabber.Exception, org.bytedeco.javacv.FrameRecorder.Exception {
		if(null != record){
			record.stop();
			record.close();
		}
		if(null != grabber){
			grabber.stop();
			grabber.close();
		}
	}

	/**
	 * 重启
	 * @param grabber
	 * @param record
	 */
	boolean reStart(FFmpegFrameGrabber grabber,FFmpegFrameRecorder record){
		boolean flag=true;
		try {
			log.error("丢包重连接开始...");
			grabber.restart();
			record.stop();
			to();
			grabber.flush();
			log.error("丢包重连接完毕...");
		}catch (Exception e){
			flag = false;
			log.error("丢包重连接异常..."+e.getMessage());
		}
		return flag;
	}

	/**
	 * 设置错误计数器
	 * @param cameraEntity
	 * @param reset 是否重置
	 */
	void setErrorCount(CameraEntity cameraEntity,boolean reset){
		if(reset){
			cameraEntity.setErrorCount(0);
		}else{
			cameraEntity.setErrorCount(cameraEntity.getErrorCount()+1);
		}
		cameraEntity.setCount(cameraEntity.getCount()+1);
		CacheUtil.STREAMMAP.put(cameraEntity.getToken(),cameraEntity);
	}
}